package com.yindian.cos.easy.security.authentication.token;

import com.yindian.cos.easy.security.authentication.login.UserDetail;
import com.yindian.cos.easy.security.authentication.login.normal.form.FormLoginLoginAuthentication;
import com.yindian.cos.easy.security.authentication.login.normal.smsCode.SmsCodeLoginLoginAuthentication;
import com.yindian.cos.easy.security.authentication.login.social.qq.QqLoginAuthentication;
import com.yindian.cos.easy.security.authentication.login.social.weibo.WeiBoLoginAuthentication;
import com.yindian.cos.easy.security.authentication.login.social.weixin.WeiXinLoginAuthentication;
import com.yindian.cos.easy.security.context.AuthenticationContext;
import com.yindian.cos.easy.security.exception.RefreshTokenExpireException;
import com.yindian.cos.easy.security.properties.TokenProperties;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;

import java.util.Date;
import java.util.UUID;

/**
 * token默认生成策略（jwt）
 *
 * @author zhangchuanhao
 * @create 2018/11/21
 */
@Log4j2
public class JwtTokenFactory extends AbstractTokenFactory {

    public static final String USER_ID = "user_id";

    public static final String TOKEN_KEY = "token_key";

    public JwtTokenFactory(TokenStore tokenStore, TokenProperties tokenProperties) {
        super(tokenStore, tokenProperties);
    }

    @Override
    public Token builder(UserDetail userDetail) {
        String tokenKey = genRedisTokenKey(userDetail.getUserId());
        TokenProperties tokenProperties = getTokenProperties();
        TokenStore tokenStore = getTokenStore();
        Token userToken = tokenStore.getTokenByUserId(tokenKey);
        if (userToken != null) {
            // 判断accessToken是否过期
            if (tokenStore.isAccessTokenNonExpired(userToken)) {
                // 删除过期的token
                tokenStore.removeToken(userToken.getAccessToken());
            } else {
                Token.refreshExpire(userToken);
            }
        } else {
            // 生成新token
            userToken = genToken(null, userDetail, tokenProperties, tokenStore);
        }
        log.debug("生成token，userId = {},token = {}", userDetail.getUserId(), userToken);
        return userToken;
    }

    @Override
    public Token refresh(String refreshToken) {
        TokenStore tokenStore = getTokenStore();
        TokenProperties tokenProperties = getTokenProperties();
        Token token = tokenStore.getTokenByRefreshToken(refreshToken);
        if (token == null) {
            throw new RefreshTokenExpireException("refresh token已过期");
        } else {
            if (tokenStore.isAccessTokenNonExpired(token)) {
                token = genToken(token.getTokenKey(), token.getUserDetail(), tokenProperties, tokenStore);
            }
            Token.refreshExpire(token);
            log.debug("刷新token，userId = {},token = {}", token.getUserId(), token);
            return token;
        }
    }

    @Override
    public boolean remove(String accessToken) {
        TokenStore tokenStore = getTokenStore();
        log.debug("删除token，access token = {}", accessToken);
        return tokenStore.removeToken(accessToken);
    }

    /**
     * 生成token并存储到redis
     *
     * @param userDetail
     * @param tokenProperties
     * @param tokenStore
     * @return
     */
    public Token genToken(String tokenKey, UserDetail userDetail, TokenProperties tokenProperties, TokenStore tokenStore) {
        if (StringUtils.isBlank(tokenKey)) {
            tokenKey = genRedisTokenKey(userDetail.getUserId());
        }
        String accessToken = genToken(userDetail, tokenProperties, tokenKey);
        String refreshToken = genToken(userDetail, tokenProperties, tokenKey);
        Token token = new Token(
                accessToken,
                tokenProperties.getAccessTokenExpire(),
                refreshToken,
                tokenProperties.getRefreshTokenExpire(),
                userDetail.getUserId(),
                userDetail,
                tokenKey
        );
        tokenStore.saveToken(token);
        Token.refreshExpire(token);
        return token;
    }


    /**
     * 生成token
     *
     * @param userDetail
     * @return
     */
    private String genToken(UserDetail userDetail, TokenProperties tokenProperties, String tokenKey) {
        return Jwts.builder()
                .signWith(SignatureAlgorithm.HS512, tokenProperties.getJwtSecretKey())
                .setIssuedAt(new Date())
                .setSubject(tokenProperties.getSubject())
                .claim(USER_ID, userDetail.getUserId())
                .claim(TOKEN_KEY, tokenKey)
                .claim("random", UUID.randomUUID().toString())
                .compact();
    }

    /**
     * 生成redis tokenKey
     *
     * @param userId
     * @return
     */
    public String genRedisTokenKey(String userId) {
        String abstractAuthenticationName = AuthenticationContext.getCurrentContext().getAuthentication();
        if (StringUtils.isBlank(abstractAuthenticationName)) {
            log.error("abstractAuthentication为null");
            throw new RuntimeException(" AuthenticationContext not found AbstractAuthentication");
        } else if (!abstractAuthenticationName.equals(FormLoginLoginAuthentication.class.getName())
                && !abstractAuthenticationName.equals(SmsCodeLoginLoginAuthentication.class.getName())
                && !abstractAuthenticationName.equals(QqLoginAuthentication.class.getName())
                && !abstractAuthenticationName.equals(WeiBoLoginAuthentication.class.getName())
                && !abstractAuthenticationName.equals(WeiXinLoginAuthentication.class.getName())) {
            log.error("abstractAuthentication不符合预期值，预期值：[AbstractLoginAuthentication、" +
                    "QqLoginAuthentication、WeiBoLoginAuthentication、WeiXinLoginAuthentication]，实际：{}", abstractAuthenticationName);
            throw new RuntimeException("abstractAuthentication不符合预期值，预期值：[AbstractLoginAuthentication、" +
                    "QqLoginAuthentication、WeiBoLoginAuthentication、WeiXinLoginAuthentication]，实际：" + abstractAuthenticationName);
        }
        String name = abstractAuthenticationName + "_" + userId;
        return name;
    }
}
